/******************************************************************************/
/* @ All Rights Reserved Copyright (C) 2014 broada,Ltd.                       */
/*                                                                            */
/*  NAME      = mysql_request.c                                               */
/*  NOTE      = MYSQL 解析请求包                                              */
/*  DATE      = 2015/04/20 by guojq                                           */
/******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <stdint.h>
#include <limits.h>

#include "mysql_core.h"

/* MySQL command codes */
#define MYSQL_SLEEP               0  /* not from client */
#define MYSQL_QUIT                1
#define MYSQL_INIT_DB             2
#define MYSQL_QUERY               3
#define MYSQL_FIELD_LIST          4
#define MYSQL_CREATE_DB           5
#define MYSQL_DROP_DB             6
#define MYSQL_REFRESH             7
#define MYSQL_SHUTDOWN            8
#define MYSQL_STATISTICS          9
#define MYSQL_PROCESS_INFO        10
#define MYSQL_CONNECT             11 /* not from client */
#define MYSQL_PROCESS_KILL        12
#define MYSQL_DEBUG               13
#define MYSQL_PING                14
#define MYSQL_TIME                15 /* not from client */
#define MYSQL_DELAY_INSERT        16 /* not from client */
#define MYSQL_CHANGE_USER         17
#define MYSQL_BINLOG_DUMP         18 /* replication */
#define MYSQL_TABLE_DUMP          19 /* replication */
#define MYSQL_CONNECT_OUT         20 /* replication */
#define MYSQL_REGISTER_SLAVE      21 /* replication */
#define MYSQL_STMT_PREPARE        22
#define MYSQL_STMT_EXECUTE        23
#define MYSQL_STMT_SEND_LONG_DATA 24
#define MYSQL_STMT_CLOSE          25
#define MYSQL_STMT_RESET          26
#define MYSQL_SET_OPTION          27
#define MYSQL_STMT_FETCH          28

/*******************************************************************************
*  FUNC     :  request信息释放
*  ARGS     :  pp_st_request(request信息)
*  RTN      :  void
*  NOTE     :
*******************************************************************************/
void mysql_destroy_request(L7_req_mysql **pp_st_request)
{
    if(pp_st_request == NULL || *pp_st_request == NULL)
        return;

    L7_req_mysql *p_st_request = *pp_st_request;
    
    if (p_st_request->p_st_statement)
    {
        /* 释放SQL statement信息 */
        frdb_statement_free(&(p_st_request->p_st_statement));
    }

    frfree(p_st_request);
    *pp_st_request = NULL;
    return;
}

/*******************************************************************************
*  FUNC     :  Frdata_field释放
*  ARGS     :  pp_st_value(Frdata_field)
*  RTN      :  void
*  NOTE     :
*******************************************************************************/
void mysql_frdata_node_free(Frlist_node *p_st_node)
{
    if( p_st_node == NULL || p_st_node->data == NULL)
    {
        return;
    }

    Frdata_field *p_st_data = (Frdata_field *)p_st_node->data;
    if (p_st_data->p_field_name)
    {
        frfree(p_st_data->p_field_name);
    }
    if (p_st_data->p_field_value)
    {
        frfree(p_st_data->p_field_value);
    }

    frfree(p_st_node->data);
    p_st_node->data = NULL;
    return;
}

/*******************************************************************************
*  FUNC     :  request信息初始化
*  ARGS     :  pp_st_request(request信息)
*  RTN      :  void
*  NOTE     :
*******************************************************************************/
int mysql_init_request(L7_req_mysql **pp_st_request)
{
    L7_req_mysql *p_st_request = (L7_req_mysql *)frmalloc(sizeof(L7_req_mysql));
    if (p_st_request == NULL)
        return FR_FAIL;
    return FR_SUCC;
}

/*******************************************************************************
*  FUNC     :  判定是否需要生成request
*  ARGS     :  p_st_private (私有数据)
*  RTN      :  FR_SUCC (true)
*              FR_FAIL (false)
*  NOTE     :  p_st_private肯定不为NULL
*******************************************************************************/
FR_STATIC int __mysql_chk_request(L7session_mysql_private *p_st_private)
{
    if (p_st_private->p_st_req)
        mysql_destroy_request(&p_st_private->p_st_req);
    if (p_st_private->p_st_req == NULL)
    {
        p_st_private->p_st_req = (L7_req_mysql *)frmalloc(sizeof(L7_req_mysql));
        if (p_st_private->p_st_req == NULL)
            return FR_FAIL;
        memset(p_st_private->p_st_req, 0x0, sizeof(L7_req_mysql));
    }

    return FR_SUCC;
}

/*******************************************************************************
*  FUNC     :  request信息解析
*  ARGS     :  p_st_private
*  RTN      :  FR_SUCC (true)
*              FR_FAIL (false)
*  NOTE     :
*******************************************************************************/
int mysql_dissect_request(Conntrack *p_st_conn
                        , Apm_packet *p_st_packet
                        , L7session_mysql_private *p_st_private
                        , Frhash_header *p_st_hash_report
                        , struct timeval *p_st_timestamp)
{
    Frbyte_Packet *p_bytebuff_packet = NULL;
    Sql_resultset *p_st_resultset = NULL;
    Sql_statement *p_st_statement = NULL;
    L7_fragment_mysql *p_st_fragment = NULL;
    L7_req_mysql *p_st_req = NULL;
    Frreport_body_data *p_st_report_body = NULL;
    unsigned long ul_req_sequence = 0;
    char *p_buffer = NULL;
    char arr_action[10];
    int i_option = 0, i_sql_size = 0;
    char *p_sql = NULL;
    int i_ret = 0;
    
    if (p_st_private == NULL || p_st_private->p_st_fragment == NULL)
        return APM_L7_ANALYZE_FAIL;
    
    p_st_fragment = p_st_private->p_st_fragment;
    /*
        1 byte  命令
        n bytes 参数
    */
    
    p_bytebuff_packet = p_st_private->p_st_fragment->p_st_data;
    if (bytebuff_get_int(p_bytebuff_packet, 1, (uint32_t *)&i_option, BYTE_BIG_ENDIAN) == FR_FAIL)
        goto err;
    
    //方向不确定的情况下，判断第一位命令以确定方向
    if( frct_get_direct_known(p_st_conn) == FR_DIRECT_UNKNOWN)
    {
        //方向错误了，重新设置方向
        if (i_option > MYSQL_STMT_FETCH)
        {
            i_ret = frcap_set_cs_srv(p_st_conn, p_st_packet->src_ip, p_st_packet->src_port);
            if (i_ret != FR_SUCC)
                frdbg("frcap_set_cs_srv failed\n");
            goto err;
        }
    }
    
    /* 判断创建request */
    i_ret = __mysql_chk_request(p_st_private);
    if (i_ret != FR_SUCC)
    {
        frdbg("__mysql_chk_request failed!\n");
        return APM_L7_ANALYZE_FAIL;
    }
    
    switch (i_option)
    {
    	case MYSQL_QUIT:
    	{
    	    p_sql = frstrdup("quit");
    	    if (p_sql == NULL)
    	        goto err;
    		break;
    	}
        case MYSQL_INIT_DB:
    	case MYSQL_CREATE_DB:
    	case MYSQL_DROP_DB:
        {
            p_buffer = (char *)frmalloc(p_st_fragment->u64_total_len);
    	    if (p_buffer == NULL)
    	        goto err;
            if (bytebuff_get_bytes(p_bytebuff_packet, p_buffer, p_st_fragment->u64_total_len - 1) == FR_FAIL)
    	    {
    	        frfree(p_buffer);
    	        goto err;
    	    }
    	    p_buffer[p_st_fragment->u64_total_len - 1] = '\0';
    	    i_sql_size = p_st_fragment->u64_total_len + sizeof(arr_action) + 1;
    	    p_sql = (char *)frmalloc(i_sql_size);
    	    if (p_sql == NULL)
    	    {
    	        frfree(p_buffer);
    	        goto err;
    	    }
    	    if (i_option == MYSQL_INIT_DB)
    	    {
                snprintf(arr_action, sizeof(arr_action), "USE");
                if (p_st_private->p_st_sess->p_db_name)
                    frfree(p_st_private->p_st_sess->p_db_name);
                p_st_private->p_st_sess->p_db_name = frstrdup(p_buffer);
            }
            else if (i_option == MYSQL_CREATE_DB)
                snprintf(arr_action, sizeof(arr_action), "CREATE");
            else if (i_option == MYSQL_DROP_DB)
                snprintf(arr_action, sizeof(arr_action), "DROP");
    	    snprintf(p_sql, i_sql_size, "%s %s", arr_action, p_buffer);
    	    frfree(p_buffer);
    	    break;
        }
        case MYSQL_STATISTICS:
    	{
    	    p_sql = frstrdup("STATUS");
    	    if (p_sql == NULL)
    	        goto err;
    		break;
    	}
    	case MYSQL_PROCESS_INFO:
    	{
    	    p_sql = frstrdup("SHOW PROCESSLIST");
    	    if (p_sql == NULL)
    	        goto err;
    		break;
    	}
	    case MYSQL_PROCESS_KILL:
	    {
	        //offset+=4
	        p_buffer = (char *)frmalloc(4+1);
    	    if (p_buffer == NULL)
    	        goto err;
            if (bytebuff_get_bytes(p_bytebuff_packet, p_buffer, 4) == FR_FAIL)
    	    {
    	        frfree(p_buffer);
    	        goto err;
    	    }
    	    p_buffer[4] = '\0';
    	    i_sql_size = 4+1 + sizeof(arr_action) + 1;
    	    p_sql = (char *)frmalloc(i_sql_size);
    	    if (p_sql == NULL)
    	    {
    	        frfree(p_buffer);
    	        goto err;
    	    }
    	    snprintf(p_sql, i_sql_size, "kill %s", p_buffer);
	        break;
	    }
    	case MYSQL_QUERY:
    	case MYSQL_STMT_PREPARE:
        //todo
    	case MYSQL_CHANGE_USER:
    	case MYSQL_FIELD_LIST:
    	{
    	    //获取sql文
    	    p_sql = (char *)frmalloc(p_st_fragment->u64_total_len);
    	    if (p_sql == NULL)
    	        goto err;
    	    
    	    memset(p_sql, 0x00, p_st_fragment->u64_total_len);
    	    
    	    if (bytebuff_get_bytes(p_bytebuff_packet, p_sql, p_st_fragment->u64_total_len - 1) == FR_FAIL)
    	    {
    	        frfree(p_sql);
    	        goto err;
    	    }
    	    break;
    	}
	    case MYSQL_STMT_CLOSE:
	    {
	        //offset+=4
	        int i_id = 0;
            if (bytebuff_get_int(p_bytebuff_packet, 4, (uint32_t *)&i_id, BYTE_LITTLE_ENDIAN) == FR_FAIL)
    	        goto err;
    	    
    	    i_sql_size = 4+1 + sizeof(arr_action) + 1;
    	    p_sql = (char *)frmalloc(i_sql_size);
    	    if (p_sql == NULL)
    	        goto err;
    	    
    	    snprintf(p_sql, i_sql_size, "close %d", i_id);
	        break;
	    }
	    case MYSQL_STMT_RESET:
	    {
	        //offset+=4
	        int i_id = 0;
            if (bytebuff_get_int(p_bytebuff_packet, 4, (uint32_t *)&i_id, BYTE_LITTLE_ENDIAN) == FR_FAIL)
    	        goto err;
    	    
    	    i_sql_size = 4+1 + sizeof(arr_action) + 1;
    	    p_sql = (char *)frmalloc(i_sql_size);
    	    if (p_sql == NULL)
    	        goto err;
    	    
    	    snprintf(p_sql, i_sql_size, "reset %d", i_id);
	        break;
	    }
	    
	    case MYSQL_REFRESH:
	    {
	        //FLUSH  offset+=1
	        if (bytebuff_skip(p_bytebuff_packet, 1) == FR_FAIL)
                goto err;
	        p_sql = frstrdup("FLUSH");
    	    if (p_sql == NULL)
    	        goto err;
	        break;
	    }
	    case MYSQL_SHUTDOWN:
	    {
	        //offset+=1
	        if (bytebuff_skip(p_bytebuff_packet, 1) == FR_FAIL)
                goto err;
	        p_sql = frstrdup("SHUTDOWN");
    	    if (p_sql == NULL)
    	        goto err;
	        break;
	    }
	    //todo
	    case MYSQL_STMT_FETCH:
	    {
	        //offset+=8
	        int i_id = 0, i_linenum = 0;
            if (bytebuff_get_int(p_bytebuff_packet, 4, (uint32_t *)&i_id, BYTE_LITTLE_ENDIAN) == FR_FAIL)
    	        goto err;
    	    
            if (bytebuff_get_int(p_bytebuff_packet, 4, (uint32_t *)&i_linenum, BYTE_LITTLE_ENDIAN) == FR_FAIL)
    	        goto err;
    	    
	        break;
	    }
	    case MYSQL_DEBUG:
	    {
	        p_sql = frstrdup("mysqladmin debug");
    	    if (p_sql == NULL)
    	        goto err;
	        break;
	    }
	    case MYSQL_PING:
	    {
	        p_sql = frstrdup("mysqladmin ping");
    	    if (p_sql == NULL)
    	        goto err;
	        break;
	    }
	    case MYSQL_STMT_SEND_LONG_DATA:
	    {
	        //调用mysql_stmt_send_long_data函数
	        /*
	            4	预处理语句的ID值（小字节序）
	            2	参数序号（小字节序）
	            n	数据负载（数据到达消息尾部时结束，无结束符）
	        */
	        if (bytebuff_skip(p_bytebuff_packet, 6) == FR_FAIL)
                goto err;
	        
	        if (p_st_fragment->u64_total_len <= 6 + 1)
	            goto err;
	        
	        if (bytebuff_get_bytes(p_bytebuff_packet, p_sql, p_st_fragment->u64_total_len - 7) == FR_FAIL)
    	    {
    	        frfree(p_sql);
    	        goto err;
    	    }
	        break;
	    }
	    case MYSQL_STMT_EXECUTE:
	    {
	        //执行预处理语句
	        break;
	    }
	    case MYSQL_BINLOG_DUMP:
	    {
	        if (bytebuff_skip(p_bytebuff_packet, p_st_fragment->u64_total_len) == FR_FAIL)
                goto err;
	        p_sql = frstrdup("BINLOG DUMP");
    	    if (p_sql == NULL)
    	        goto err;
	        break;
	    }
    	case MYSQL_TABLE_DUMP:
    	{
    	    //已过时
    	    if (bytebuff_skip(p_bytebuff_packet, p_st_fragment->u64_total_len) == FR_FAIL)
                goto err;
	        p_sql = frstrdup("TABLE DUMP");
    	    if (p_sql == NULL)
    	        goto err;
	        break;
    	}
    	case MYSQL_REGISTER_SLAVE:
    	{
    	    //备份注册
    	    if (bytebuff_skip(p_bytebuff_packet, p_st_fragment->u64_total_len) == FR_FAIL)
                return FR_FAIL;
	        p_sql = frstrdup("REGISTER SLAVE");
    	    if (p_sql == NULL)
    	        goto err;
	        break;
    	}
    	case MYSQL_SET_OPTION:
    	{
    	    //offset+=2
	        int i_id = 0;
            if (bytebuff_get_int(p_bytebuff_packet, 2, (uint32_t *)&i_id, BYTE_LITTLE_ENDIAN) == FR_FAIL)
    	        goto err;
    	    
    	    i_sql_size = 2+1 + sizeof(arr_action) + 1;
    	    p_sql = (char *)frmalloc(i_sql_size);
    	    if (p_sql == NULL)
    	        goto err;
    	    
    	    snprintf(p_sql, i_sql_size, "set option %d", i_id);
	        break;
    	}
    	case MYSQL_CONNECT_OUT:
    	{
    	    //跳过数据长度
            if (bytebuff_skip(p_bytebuff_packet, p_st_fragment->u64_total_len - 1) == FR_FAIL)
                return FR_FAIL;
            break;
    	}
    	default:
	    {
	        //方向不确定的情况下，判断第一位命令以确定方向
            if( frct_get_direct_known(p_st_conn) == FR_DIRECT_UNKNOWN)
            {
                i_ret = frcap_set_cs_srv(p_st_conn, p_st_packet->src_ip, p_st_packet->src_port);
                if (i_ret != FR_SUCC)
                    frdbg("frcap_set_cs_srv failed\n");
                goto err;
            }
            else
                goto err;
	    }
	}
	
    /* 创建statement */
    p_st_statement = frdb_create_statement();
    if (p_st_statement == NULL)
    {
        frdbg("Create sqlbatch statement failed!\n");
        if (p_sql)
            frfree(p_sql);
        goto err;
    }
    
    /* 创建结果集 */
    ul_req_sequence = frct_seq_increase(p_st_conn);
    p_st_resultset = frdb_create_resultset(ul_req_sequence);
    if (p_st_resultset == NULL)
    {
        frdbg("Create sqlbatch resultset failed!\n");
        frdb_statement_free(&p_st_statement);
        if (p_sql)
            frfree(p_sql);
        goto err;
    }

    /* 追加结果集到statement */
    i_ret = frdb_add_resultset(p_st_statement, p_st_resultset);
    if (i_ret != FR_SUCC)
    {
        frdbg("Insert resultset into statement failed!\n");
        frdb_statement_free(&p_st_statement);
        frdb_resultset_free(&p_st_resultset);
        if (p_sql)
            frfree(p_sql);
        goto err;
    }
    
    frdb_set_sql(p_st_statement, p_sql);
    
    p_st_fragment->st_end_timestamp.tv_sec = p_st_timestamp->tv_sec;
    p_st_fragment->st_end_timestamp.tv_usec = p_st_timestamp->tv_usec;
    
    /* 更新请求结果集信息 */
    frdb_set_resultset_req2(p_st_resultset
                           , p_st_fragment->u64_total_len
                           , &(p_st_fragment->st_first_timestamp)
                           , &(p_st_fragment->st_end_timestamp));
    /* 设置statement和resultset的索引 */
    p_st_req = p_st_private->p_st_req;
    p_st_req->p_st_statement = p_st_statement;
    p_st_req->p_st_resultset = p_st_resultset;
    
    p_st_report_body = mysql_new_report_body(p_st_conn, p_st_private->p_st_sess);
    /* 创建report body失败时，不释放做成的request */
    if (p_st_report_body == NULL)
    {
        frdbg("mysql_new_report_body failed!\n");
        return APM_L7_ANALYZE_SUCC;
    }
    
    //上报request信息
    i_ret = frdb_report_req2(p_st_conn, p_st_hash_report, p_st_statement
                             , p_st_resultset, &p_st_report_body);
    /* 上报request数据失败时，不释放做成的request */
    if (i_ret != FR_SUCC)
    {
        frdbg("frdb_report_req2 failed!\n");
        frreport_body_free(&p_st_report_body);
        return APM_L7_ANALYZE_SUCC;
    }

    return APM_L7_ANALYZE_SET_REQ(APM_L7_ANALYZE_SUCC);
err:
    return APM_L7_ANALYZE_FAIL;
}

